﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;

/**
 * Class that is primarily responsible for generating code
 * 
 * Generates a class for the group of sequences producing the same object type
 * Generates a method for each sequence
 **/ 
namespace MethodSeqGenerator
{
    class CodeGenerator
    {
        String namespace_name = ""; //A common namespace under which all generated classes are kept
        HashSet<String> usingStatements = null;
        private StreamWriter currentOutFile = null;
        private int methodIdGenerator = 0;
        HashSet<String> additionalUsingStmts = null;

        public CodeGenerator(String namespace_name)
        {
            usingStatements = new HashSet<string>();
            additionalUsingStmts = new HashSet<String>();
            LoadStandardUsingStatements();
            this.namespace_name = namespace_name;
        }

        //Generating all sequences
        public void GenerateCode(Hashtable sequenceStore)
        {            
            foreach (String searchString in sequenceStore.Keys)
            {
                additionalUsingStmts.Clear();
                String actual_className;
                int lastIndex = searchString.LastIndexOf(".");
                if(lastIndex == -1) 
                    actual_className = searchString;
                else
                    actual_className = searchString.Substring(lastIndex + 1, searchString.Length - lastIndex - 1);

                currentOutFile = new StreamWriter(new FileStream(SeqGenerator.FACTORY_OUTPUT_DIR + "\\"
                    + actual_className + "_factory" + (fileNameRandGenerator++) + ".cs", FileMode.Create, FileAccess.Write));

                addToNamespaces(searchString);

                List<String> programCode = new List<String>();

                programCode.Add("namespace " + namespace_name);
                programCode.Add("{");
                programCode.Add("\t[PexFactoryClass]");
                programCode.Add("\tpublic partial class " + actual_className + "_factory");
                programCode.Add("\t{");
                
                SequenceGroup sgObj = (SequenceGroup) sequenceStore[searchString];
                List<SequenceStore> seqStoreList = sgObj.sequenceGrpList;
                int seqID = 1;
                foreach (SequenceStore ssObj in seqStoreList)
                {
                    programCode.Add("//Sequence ID : " + seqID + ", Frequency: " + ssObj.frequency + " ProducesInstance: " + ssObj.includeConstructor);
                    programCode.Add("//DLLName: " + ssObj.dllName + ", ClassName: " + ssObj.typedeclName + ", MethodName: " + ssObj.methodName);
                    PrintMethodInvocationSequence(searchString, ssObj.sequence, ssObj, programCode);
                    seqID++;
                }
                programCode.Add("\t}");
                programCode.Add("}");

                //Printing standard using statements
                foreach (String stdUsing in usingStatements)
                    currentOutFile.WriteLine(stdUsing);

                //Printing additional using statements
                foreach (String addUsing in additionalUsingStmts)
                    currentOutFile.WriteLine(addUsing);

                //Printing program code
                foreach (String progCode in programCode)
                    currentOutFile.WriteLine(progCode);

                currentOutFile.Close();
            }
        }

        private void PrintMethodInvocationSequence(String searchString, List<MethodInvocationHolder> msequence, 
                SequenceStore ssObj, List<String> programCode)
        {
            String methodHeader = "\t\t\tpublic static " + searchString + " Create" + methodIdGenerator++ + "(";
            List<String> methodBody = new List<String>();

            bool bValuesAddedToHeader = false;
            HashSet<String> returnVariables = new HashSet<string>();
            MethodInvocationHolder firstMIH = msequence[0];
            String dummyVar = GetDummyVarName(firstMIH.returnType.getTypeName());

            String outputStr = "";
            if (ssObj.includeConstructor)
            {
                if (firstMIH.methodName.Equals("CONSTRUCTOR"))
                {
                    outputStr = firstMIH.referenceClsName.getTypeName() + " " + dummyVar + " = new "
                        + firstMIH.referenceClsName.getTypeName() + " (";
                    String[] argList = ParseArgumentString(firstMIH.argStringAr, returnVariables);
                    if (firstMIH.argStringAr.Count > 0)
                    {
                        outputStr = outputStr + argList[1];
                        methodHeader = methodHeader + argList[0];
                        bValuesAddedToHeader = true;
                    }
                    outputStr = outputStr + "); //" + firstMIH.argumentString;
                }
                else
                {
                    addToNamespaces(firstMIH.returnType.getTypeName());
                    outputStr = firstMIH.returnType.getTypeName() + " " + dummyVar + " = ";
                    if (firstMIH.bStatic)
                        outputStr = outputStr + firstMIH.referenceClsName.getTypeName();
                    else
                    {
                        String newDummyVarName = GetDummyVarName(firstMIH.referenceClsName.getTypeName());
                        methodHeader = methodHeader + firstMIH.referenceClsName.getTypeName() + " " + newDummyVarName;
                        bValuesAddedToHeader = true;
                        outputStr = outputStr + newDummyVarName;
                    }

                    outputStr = outputStr + "." + firstMIH.methodName + "(";
                    String[] argList = ParseArgumentString(firstMIH.argStringAr, returnVariables);
                    if (firstMIH.argStringAr.Count > 0)
                    {
                        outputStr = outputStr + argList[1];
                        if (bValuesAddedToHeader)
                            methodHeader = methodHeader + "," + argList[0];
                        else
                        {
                            methodHeader = methodHeader + argList[0];
                            bValuesAddedToHeader = true;
                        }                        
                    }
                    outputStr = outputStr + "); //" + firstMIH.argumentString;
                }
            }
            else
            {
                methodHeader = methodHeader + firstMIH.referenceClsName.getTypeName() + " " + dummyVar;
                bValuesAddedToHeader = true;
                if (!firstMIH.bPropertyReference)
                {
                    String[] argList = ParseArgumentString(firstMIH.argStringAr, returnVariables);
                    outputStr = dummyVar + "." + firstMIH.methodName + "(";
                    if (firstMIH.argStringAr.Count > 0)
                    {
                        outputStr = outputStr + argList[1];
                        if (bValuesAddedToHeader)
                            methodHeader = methodHeader + "," + argList[0];
                        else
                        {
                            methodHeader = methodHeader + argList[0];
                            bValuesAddedToHeader = true;
                        }
                    }
                    outputStr = outputStr + "); //" + firstMIH.argumentString;
                }
                else
                {
                    outputStr = dummyVar + "." + firstMIH.methodName + " = " + firstMIH.propertyReferenceValue + ";";
                }
            }
            methodBody.Add("\t\t\t\t" + outputStr + "//" + "(Depth: " + firstMIH.nestingDepth + ")");
            
            bool bSkipFirst = true;
            foreach (MethodInvocationHolder mihObj in msequence)
            {
                if (bSkipFirst)
                {
                    bSkipFirst = false;
                    continue;
                }

                if (!mihObj.bPropertyReference)
                {
                    String[] argStr = ParseArgumentString(mihObj.argStringAr, returnVariables);
                    String mBodyStr = "\t\t\t\t";
                    if (!mihObj.returnType.getTypeName().Equals("void") && !mihObj.returnType.getTypeName().Equals("System.Void"))
                    {
                        mBodyStr = mBodyStr + mihObj.returnType.getTypeName() + " " + mihObj.returnType.getVarName() + " = ";
                        returnVariables.Add(mihObj.returnType.getVarName());
                    }

                    mBodyStr = mBodyStr + dummyVar + "." + mihObj.methodName;
                    if (!mihObj.bPropertyReference)
                    {
                        mBodyStr = mBodyStr + "(";
                        if (mihObj.argStringAr.Count > 0)
                        {
                            mBodyStr = mBodyStr + argStr[1];
                            if (!argStr[0].Equals(""))
                                if (bValuesAddedToHeader)
                                    methodHeader = methodHeader + "," + argStr[0];
                                else
                                {
                                    methodHeader = methodHeader + argStr[0];
                                    bValuesAddedToHeader = true;
                                }
                        }
                        mBodyStr = mBodyStr + ")";
                    }

                    mBodyStr = mBodyStr + ";// " + mihObj.argumentString + "(Depth: " + mihObj.nestingDepth + ")";
                    methodBody.Add(mBodyStr);
                }
                else
                {
                    methodBody.Add("\t\t\t\t" + dummyVar + "." + mihObj.methodName + " = " + mihObj.propertyReferenceValue + ";");
                }
            }

            methodHeader = methodHeader + ")";
            programCode.Add(methodHeader);
            programCode.Add("\t\t\t{");

            foreach (String str in methodBody)
                programCode.Add(str);

            programCode.Add("\t\t\t\treturn " + dummyVar + ";");            
            programCode.Add("\t\t\t}");
        }

        int fileNameRandGenerator = 0;
        int randGenerator = 0;
        private String GetDummyVarName(String objType)
        {
            int lastDot = objType.LastIndexOf(".");
            if (lastDot != -1)
                objType = objType.Substring(lastDot + 1, objType.Length - lastDot - 1);

            return objType + "_obj_" + (randGenerator++);
        }

        private void LoadStandardUsingStatements()
        {
            usingStatements.Add("using Microsoft.Pex.Framework;");
            usingStatements.Add("using Microsoft.Pex.Framework.Factories;");
            usingStatements.Add("using System;");
            usingStatements.Add("using System.Collections.Generic;");
            usingStatements.Add("using System.Text;");
        }

        //Accepts a list of type holders and returns two kinds of strings:
        //1. String for the method definition
        //2. String for the method call
        private String[] ParseArgumentString(List<TypeHolder> argList, HashSet<String> returnVariables)
        {
            if (argList.Count == 0)
                return null;

            String methodDefitionStr = "";
            String methodCallStr = "";

            TypeHolder lastTypeHolder = argList[argList.Count - 1];
            foreach (TypeHolder tpObj in argList)
            {
                if (returnVariables.Contains(tpObj.getVarName()))
                {
                    //This variable is produced by another method invocation in the sequece
                    methodCallStr = methodCallStr + tpObj.getVarName();
                    if (tpObj != lastTypeHolder)
                        methodCallStr = methodCallStr + ",";
                    continue;
                }

                addToNamespaces(tpObj.getTypeName());
                String dVarName = GetDummyVarName(tpObj.getTypeName());
                methodDefitionStr = methodDefitionStr + tpObj.getTypeName() + " " + dVarName;
                methodCallStr = methodCallStr + dVarName;
                if (tpObj != lastTypeHolder)
                {
                    methodDefitionStr = methodDefitionStr + ",";
                    methodCallStr = methodCallStr + ",";
                }
            }

            return new String[] { methodDefitionStr, methodCallStr };
        }

        //Computes the entire list of namespaces belonging to the class file
        private void addToNamespaces(String className)
        {
            int lastIndex = className.LastIndexOf(".");
            if (lastIndex == -1 || lastIndex == 0)
                return;
            String namespaceName = className.Substring(0, lastIndex);
            additionalUsingStmts.Add("using " + namespaceName + ";");  
        }
    }
}
